﻿using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;
using System;

namespace Framework.Extensions
{
    public static class TypeExtensions
    {

        public static string ClassName(this Type t)
        {
            return t.Name.Replace(t.Namespace + ".", "");
        }

        public static bool IsAnonymous(this Type type)
        {
            return type.Name.IndexOf("Anonymous", StringComparison.OrdinalIgnoreCase) > -1;
        }

        public static IEnumerable<Type> GetBaseTypes(this Type itemType)
        {
            var list = new List<Type>();

            while (itemType.BaseType != null && itemType.BaseType != typeof(object))
            {
                list.Add(itemType.BaseType);
                itemType = itemType.BaseType;
            }
            return list;
        }

        public static List<T> GetAttributes<T>(this Type item) where T : Attribute
        {
            List<T> result = new List<T>();
            foreach (var attribute in item.GetCustomAttributes(typeof(T), true))
            {
                result.Add(attribute as T);
            }

            return result;
        }

        public static object CallStaticMethod(this Type objectType, string methodName)
        {
            return objectType.CallStaticMethod(methodName, null);
        }

        public static object CallStaticMethod(this Type objectType, string methodName, object[] parameters)
        {
            var mi = objectType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Static);
            if (mi != null)
                return mi.Invoke(null, parameters);

            throw new ApplicationException(String.Format("Unknown static method {0} in class {1}", methodName, objectType.FullName));
        }

        public static object GetStaticValue(this Type objectType, string name)
        {
            var pi = objectType.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Static);
            if (pi != null)
                return pi.GetGetMethod().Invoke(null, null);

            throw new ApplicationException(String.Format("Unknown static method {0} in class {1}", name, objectType.FullName));
        }


        public static bool Match(this Type item, Type criteriaType)
        {
            //return (typeof(Attribute).IsAssignableFrom(criteriaType)) ? item.IsDefined(criteriaType, true) : criteriaType.IsAssignableFrom(item);
            if (criteriaType.IsAssignableFrom(item)) return true;

            if (typeof(Attribute).IsAssignableFrom(criteriaType))
            {
                return item.IsDefined(criteriaType, true);
            }

            if (criteriaType.IsGenericTypeDefinition)
            {
                if (item.IsGenericType)
                {
                    if (item.GetGenericTypeDefinition() == criteriaType) return true;
                }
                if (item.BaseType != null && item.BaseType != typeof(object))
                {
                    var parent = item.BaseType;
                    if (parent.Match(criteriaType)) return true;
                }
                var interfaces = item.GetInterfaces();
                if (interfaces != null)
                {
                    foreach (var interfaceType in interfaces)
                    {
                        if (interfaceType.Match(criteriaType)) return true;
                    }
                }
            }

            return false;
        }

        public static IEnumerable<Type> GetMatchingTypes(this Type criteriaType)
        {
            return criteriaType.GetMatchingTypes(x => true);
        }

        public static IEnumerable<Type> GetMatchingTypes(this Type criteriaType, Func<Type, bool> where)
        {
            return criteriaType.GetMatchingTypes(null, where);
        }

        public static IEnumerable<Type> GetMatchingTypes(this Type criteriaType, string directoryPath, Func<Type, bool> where)
        {
            Func<Type, bool> minimumCriterias = x => x != criteriaType && where(x);
            var allTypes = GetTypesInDirectory(directoryPath).ToList();
            var filtered = allTypes.Where(x => x.Match(criteriaType));
            return filtered.Where(minimumCriterias);
        }

        public static IEnumerable<Type> GetTypesInDirectory(string directoryPath)
        {
            var result = new List<Type>();
            LoadLibraries(directoryPath);
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var assembly in assemblies)
            {
                Type[] elems = null;

                try
                {
                    elems = assembly.GetTypes();
                }
                catch
                {
                }

                if (elems != null) result.AddRange(elems);
            }
            return result;
        }

        private static void LoadLibraries(string directoryPath)
        {
            var loadedAssemblyNames = AppDomain.CurrentDomain
                                        .GetAssemblies()
                                        .Select(x => x.GetName().Name);

            if (!string.IsNullOrWhiteSpace(directoryPath)) directoryPath = Environment.CurrentDirectory;

            if (directoryPath != null)
            {
                var directoryInfo = new DirectoryInfo(directoryPath);
                if (directoryInfo.Exists)
                {
                    directoryInfo
                        .GetFiles("*.dll", SearchOption.AllDirectories)
                        .Select(y => new { FullName = y.FullName, Name = Path.GetFileNameWithoutExtension(y.Name) })
                        .Where(x => !loadedAssemblyNames.Contains(x.Name))
                        .ForEach(x =>
                        {
                            try { Assembly.LoadFrom(x.FullName); }
                            catch (BadImageFormatException) { /* assemblyFile is not a valid assembly OR Version 2.0 or later of the common language runtime is currently loaded and assemblyFile was compiled with a later version */ }
                            catch (FileLoadException) { /* A file that was found could not be loaded */}
                        });
                }
            }
        }

        public static IEnumerable<Type> GetTypes()
        {
            return GetTypesInDirectory(AppDomain.CurrentDomain.BaseDirectory);
        }
        
        public static object GetOne(this Type objectType)
        {
            return Framework.Reflection.FastObjectFactory.Create(objectType);
        }

        public static object GetOne(this Type objectType, params object[] args)
        {
            return Framework.Reflection.FastObjectFactory.Create(objectType, args);
        }

        public static T GetOne<T>(this Type objectType)
        {
            return (T)GetOne(objectType);
        }

        public static T GetOne<T>(this Type objectType, params object[] args)
        {
            return (T)GetOne(objectType, args);
        }

        public static IEnumerable<T> GetOne<T>(this IEnumerable<Type> objectType)
        {
            return objectType.Select(x => x.GetOne<T>());
        }

        public static Type GetMemberType(this Type objectType, string memberName)
        {
            var pi = objectType.GetProperty(memberName);
            if (pi != null) return pi.PropertyType;

            var fi = objectType.GetField(memberName);
            return fi != null ? fi.FieldType : default(Type);
        }

        public static IEnumerable<PropertyInfo> GetPublicProperties(this Type t)
        {
            return t.GetProperties(BindingFlags.Instance | BindingFlags.Public);
        }
        public static IEnumerable<PropertyInfo> GetAllPublicProperties(this Type t)
        {
            return t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
        }
    }
}